﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using DistributedVisionRunner.Interface.Model;
using NetMQ;
using NetMQ.Sockets;
using Newtonsoft.Json;

namespace DistributedVisionRunner.Interface.Client
{
    /// <summary>
    /// Use this class when vision runner is in a separate app
    /// </summary>
    public class DistributedVisionClient : IDisposable, IVisionClient
    {
        private readonly RequestSocket _requestSocket;
        private readonly object _locker = new object();


        /// <summary>
        /// 
        /// </summary>
        /// <param name="serverAddress">
        /// The ZeroMQ address to the vision runner server, for example, tcp://localhost:6001
        /// </param>
        public DistributedVisionClient(string serverAddress)
        {
            _requestSocket = new RequestSocket(serverAddress);
        }

        public DistributedVisionRunnerResultPack RequestProcess(string inputSn, string productType, int cavity,
            byte[] data, DateTime? creationTime = null, int? timeout = null)
        {
            var sn = string.IsNullOrEmpty(inputSn) ? string.Empty : inputSn;
            if (cavity < 1) throw new ArgumentException("Cavity can not be less than 1", nameof(cavity));
            if (data == null || !data.Any()) throw new ArgumentException("Data can not be empty or null", nameof(data));
            if (timeout != null && timeout.Value <= 0) throw new ArgumentException("Timeout must be a positive number", nameof(timeout));

            lock (_locker)
            {
                var timeStamp = creationTime?.ToString("yyyy-MMdd-HH-mm-ss-fff") ?? string.Empty;
                _requestSocket
                    .SendMoreFrame("ProcessRequest")
                    .SendMoreFrame(productType)
                    .SendMoreFrame(sn)
                    .SendMoreFrame(cavity.ToString())
                    .SendMoreFrame(timeStamp)
                    .SendFrame(data);

                if (timeout == null)
                {
                    var response = _requestSocket.ReceiveFrameString();
                    return JsonConvert.DeserializeObject<DistributedVisionRunnerResultPack>(response);
                }

                return WaitForResponseWithTimeOut<DistributedVisionRunnerResultPack>(_requestSocket, timeout.Value);
            }
        }

        private static T WaitForResponseWithTimeOut<T>(RequestSocket requestSocket, int timeout)
        {
            string response = null;
            bool? responseTaskWin = null;
            var resetEvent = new ManualResetEvent(false);

            // Respond task
            new Thread(() =>
                {
                    response = requestSocket.ReceiveFrameString();
                    if (!responseTaskWin.HasValue) responseTaskWin = true;
                    resetEvent.Set();
                })
            { IsBackground = true }.Start();

            // Time out task
            new Thread(() =>
                {
                    Thread.Sleep(timeout);
                    if (!responseTaskWin.HasValue) responseTaskWin = false;
                    resetEvent.Set();
                })
            { IsBackground = true }.Start();

            resetEvent.WaitOne();


            if (responseTaskWin == true)
            {
                return JsonConvert.DeserializeObject<T>(response);
            }

            throw new TimeoutException("Process response can not return in time");
        }

        public Dictionary<OutputDataType, List<BoundaryItem>> GetBoundaryItems(int? timeout = null)
        {
            Dictionary<OutputDataType, List<BoundaryItem>> BoundaryDictFromCollection(BoundaryItemsCollection collection)
            {
                var output = new Dictionary<OutputDataType, List<BoundaryItem>>
                {
                    [OutputDataType.Raw] = collection.RawBoundaries,
                    [OutputDataType.Weighted] = collection.WeightedBoundaries,
                    [OutputDataType.Reserved] = collection.ReservedBoundaries
                };
                return output;
            }


            lock (_locker)
            {
                _requestSocket.SendFrame("BoundaryItemsRequest");

                BoundaryItemsCollection collection = null;
                if (timeout == null)
                {
                    var response = _requestSocket.ReceiveFrameString();
                    collection = JsonConvert.DeserializeObject<BoundaryItemsCollection>(response);
                    return BoundaryDictFromCollection(collection);
                }

                collection = WaitForResponseWithTimeOut<BoundaryItemsCollection>(_requestSocket, timeout.Value);
                return BoundaryDictFromCollection(collection);

            }
        }

        public event Action<Dictionary<OutputDataType, List<BoundaryItem>>> BoundaryItemsChanged;

        public void Close()
        {
            lock (_locker)
            {
                _requestSocket.Close();
            }
        }

        public void Dispose()
        {
            _requestSocket.Dispose();
        }
    }
}